package br.org.espmessenger.modelo;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.hibernate.annotations.Cascade;

import br.org.espmessenger.cliente.AgenteDeInfraEstrutura;
import br.org.espmessenger.middleware.InfraEstruturaException;
import br.org.espmessenger.modelo.excecoes.ConviteJaEnviadoException;
import br.org.espmessenger.modelo.excecoes.UsuarioInexistenteException;

@Entity
@Table(name = "Usuario")
public class Usuario implements Serializable {
	private static final long serialVersionUID = 1L;

	private int id;
	private String nome;
	private String nick;
	private String email;
	private String senha;
	private byte[] avatar;
	private Set<Conversa> conversas = new HashSet<Conversa>();
	private List<Mensagem> mensagensEnviadas = new ArrayList<Mensagem>();
	private Set<MensagemConvite> convitesRecebidos = new TreeSet<MensagemConvite>();
	private Set<Grupo> grupos = new TreeSet<Grupo>();
	private Status status;

	public Usuario() {
	}

	/**
	 * Construtor que já preenche os atributos requeridos
	 * 
	 * @param email
	 * @param senha
	 */
	public Usuario(String email, String senha) {
		this.email = email;
		this.senha = senha;
	}

	public void convidar(Usuario convidado, String texto)
		throws InfraEstruturaException, UsuarioInexistenteException, ConviteJaEnviadoException {
		// 1. Criar o convite
		MensagemConvite convite = criarConvite(convidado, texto);
		// 2. Checar se o convite já foi enviado
		MensagemConvite conviteJaEnviado = obterConviteJaEnviado(convite);
		if (conviteJaEnviado != null) {
			// 2.1 Tratar a situação do convite enviado
			String situacao = tratarSituacao(conviteJaEnviado);
			throw new ConviteJaEnviadoException("Este convite já foi enviado e se encontra " + situacao);
		}
		// 3. Registrar o convite nas mensagens enviadas
		mensagensEnviadas.add(convite);
		
		try {
			AgenteDeInfraEstrutura.getInstance().getMessenger().registrarConvite(convite);
		} catch (RemoteException e) {
			throw new InfraEstruturaException(e);
		}
	}

	private String tratarSituacao(MensagemConvite conviteJaEnviado) {
		switch (conviteJaEnviado.getSituacao()) {
			case ACEITO:
				return "aceito";
			case NEGADO:
			case PENDENTE:
				return "pendente";
		}
		return null;
	}

	/**
	 * Método interno que cria um novo convite e já o registra como enviado
	 * @param convidado
	 * @param texto
	 * @return MensagemConvite
	 */
	private MensagemConvite criarConvite(Usuario convidado, String texto) {
		MensagemConvite convite = new MensagemConvite();
		convite.setRemetente(this);
		convite.setConvidado(convidado);
		convite.setTexto(texto);
		convite.setSituacao(Situacao.PENDENTE);
		return convite;
	}

	private MensagemConvite obterConviteJaEnviado(MensagemConvite convite) {
		for (Mensagem m : mensagensEnviadas) {
			if (convite.equals(m)) return convite;
		}

		return null;
	}

	public MensagemConversa criarMensagem(Conversa conversa, String texto) {
		MensagemConversa mensagem = new MensagemConversa();
		mensagem.setConversa(conversa);
		mensagem.setRemetente(this);
		mensagem.setMomento(new Date());
		mensagem.setTexto(texto);
		return mensagem;
	}

	public void enviarMensagem(Conversa conversa, String texto)
	throws InfraEstruturaException {
		MensagemConversa mensagem = criarMensagem(conversa, texto);
		conversa.notificarParticipantes(mensagem);
	}

	public void enviarMensagem(Usuario destinatario, String texto)
	throws InfraEstruturaException {
		Conversa conversa = new Conversa(new Date());
		conversa.adicionarParticipante(this);
		conversa.adicionarParticipante(destinatario);
		enviarMensagem(conversa, texto);
	}

	@Id
	@GeneratedValue(generator = "genusuario", strategy=GenerationType.AUTO)
	@SequenceGenerator(name = "genusuario", sequenceName = "usuario_id_seq", initialValue = 1, allocationSize = 1)
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	@Column(length = 255)
	public String getNome() {
		return nome;
	}

	public void setNome(String nome) {
		this.nome = nome;
	}

	public String getNick() {
		return nick;
	}

	public void setNick(String nick) {
		this.nick = nick;
	}

	@Column(nullable = false, unique = true)
	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	@Column(nullable = false)
	public String getSenha() {
		return senha;
	}

	public void setSenha(String senha) {
		this.senha = senha;
	}

	@Lob
	public byte[] getAvatar() {
		return avatar;
	}

	public void setAvatar(byte[] avatar) {
		this.avatar = avatar;
	}

	@ManyToMany(mappedBy = "participantes", cascade=CascadeType.ALL)
	public Set<Conversa> getConversas() {
		return conversas;
	}

	public void setConversas(Set<Conversa> conversas) {
		this.conversas = conversas;
	}

	@OneToMany(mappedBy = "remetente", cascade = CascadeType.ALL)
	public List<Mensagem> getMensagensEnviadas() {
		return mensagensEnviadas;
	}

	public void setMensagensEnviadas(List<Mensagem> mensagensEnviadas) {
		this.mensagensEnviadas = mensagensEnviadas;
	}

	@OneToMany(mappedBy = "convidado", cascade=CascadeType.ALL)
	public Set<MensagemConvite> getConvitesRecebidos() {
		return convitesRecebidos;
	}

	public void setConvitesRecebidos(
			Set<MensagemConvite> convitesRecebidos) {
		this.convitesRecebidos = convitesRecebidos;
	}

	@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, fetch=FetchType.LAZY)
	@Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
	public Set<Grupo> getGrupos() {
		return grupos;
	}

	public void setGrupos(Set<Grupo> grupos) {
		this.grupos = grupos;
	}

	@Transient
	public Status getStatus() {
		return status;
	}

	public void setStatus(Status status) {
		this.status = status;
	}

	public static long getSerialVersionUID() {
		return serialVersionUID;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((email == null) ? 0 : email.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Usuario other = (Usuario) obj;
		if (email == null) {
			if (other.email != null)
				return false;
		} else if (!email.equals(other.email))
			return false;
		return true;
	}

}
